/*
 *   Copyright 2021 zzh
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.aduib.boot.common.SPI.metadata;

import com.aduib.boot.common.SPI.ConfigurableSPIFactory;
import com.aduib.boot.common.SPI.FactoryUtils;
import com.aduib.boot.common.SPI.SPIDefinition;
import com.aduib.boot.common.exception.CommonException;
import com.aduib.boot.common.util.ArrayUtils;
import com.aduib.boot.common.util.ClassUtils;
import com.aduib.boot.common.util.ListUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;

/**
 * @description: MetaDataBuilder
 * @author: zzh
 * @date: 2021/11/1 14:06
 */
public class MetaDataBuilder {

  public static List<ConstructorMetaData> buildConstructorMetaData(Class<?> clazz, ConfigurableSPIFactory spiFactory) {
    List<ConstructorMetaData> list = ListUtils.list(false);
    Constructor<?>[] constructors = ClassUtils.getConstructors(clazz);
    for (Constructor<?> constructor : constructors) {
      DefaultConstructorMetaData constructorMetaData =
          new DefaultConstructorMetaData(
                  constructor,
                  ClassUtils.getClassName(clazz, true),
                  constructor.getModifiers(),
                  constructor.getAnnotations(),
                  constructor.getParameterTypes(),
                  ArrayUtils.EMPTY_OBJECT_ARRAY,
              constructor.getExceptionTypes());
      list.add(constructorMetaData);
    }
    return list;
  }

  public static List<MethodMetaData> buildMethodMetaData(Class<?> clazz, ConfigurableSPIFactory spiFactory) {
    List<MethodMetaData> list = ListUtils.list(false);
    Method[] methods = ClassUtils.getDeclaredMethods(clazz);
    for (Method method : methods) {
      DefaultMethodMetaData methodMetaData =
              new DefaultMethodMetaData(
                      method,
                      ClassUtils.getClassName(clazz, true),
                      method.getModifiers(),
                      method.getAnnotations(),
                      method.getParameterTypes(),
                      ArrayUtils.EMPTY_OBJECT_ARRAY,
                      method.getExceptionTypes(),
                      method.getReturnType());
      list.add(methodMetaData);
    }
    return list;
  }

  public static List<FieldMetaData> buildFieldMetaData(Class<?> clazz, ConfigurableSPIFactory spiFactory) {
    List<FieldMetaData> list = ListUtils.list(false);
    Field[] fields = ClassUtils.getFields(clazz);
    for (Field field : fields) {
      DefaultFieldMetaData fieldMetaData =
              new DefaultFieldMetaData(
                      field.getAnnotations(),
                      field,
                      ClassUtils.getClassName(clazz, true),
                      field.getModifiers(),
                      field.getType(),
                      ArrayUtils.EMPTY_OBJECT_ARRAY);
      list.add(fieldMetaData);
    }
    return list;
  }

  /**
   * 创建参数值
   *
   * @param constructorMetaData
   * @param spiFactory
   * @return
   */
  public static Object[] buildParameterValues(ConstructorMetaData constructorMetaData, ConfigurableSPIFactory spiFactory) {
    if (constructorMetaData.getParameterCount() == 0) {
      return ArrayUtils.EMPTY_OBJECT_ARRAY;
    } else {
      Class<?>[] parameterTypes = ((Executable) constructorMetaData.getAccessibleObject()).getParameterTypes();
      List<Object> parameters = ListUtils.list(false);
      for (Class<?> parameterType : parameterTypes) {
        if (FactoryUtils.withSpiAnnotation(parameterType)) {
          try {
            parameters.add(spiFactory.getSPI(parameterType));
          } catch (Exception ignored) {
            throw new CommonException(ignored);
          }
        }else{
          parameters.add(ClassUtils.getPrimitiveTypeValue(parameterType));
        }
      }
      return parameters.toArray();
    }
  }

  public static MethodMetaData getConstructorMetaData(Method method,SPIDefinition definition){
    List<MethodMetaData> methodMetaDataList = definition.getMethodMetaDataList();
    if(Objects.isNull(methodMetaDataList)|| methodMetaDataList.isEmpty()){
      return null;
    }else{
      MethodMetaData methodMetaData= null;
      for (MethodMetaData metaData : methodMetaDataList) {
        if(metaData.getAccessibleObject().equals(method)){
          methodMetaData=metaData;
          break;
        }
      }
      return methodMetaData;
    }
  }

  public static FieldMetaData getConstructorMetaData(Field field,SPIDefinition definition){
    List<FieldMetaData> fieldMetaDataList = definition.getFieldMetaDataList();
    if(Objects.isNull(fieldMetaDataList)|| fieldMetaDataList.isEmpty()){
      return null;
    }else{
      FieldMetaData fieldMetaData= null;
      for (FieldMetaData metaData : fieldMetaDataList) {
        if(metaData.getAccessibleObject().equals(field)){
          fieldMetaData=metaData;
          break;
        }
      }
      return fieldMetaData;
    }
  }

  public static ConstructorMetaData getConstructorMetaData(Constructor<?> constructor,SPIDefinition definition){
    List<ConstructorMetaData> constructorMetaDataList = definition.getConstructorMetaDataList();
    if(Objects.isNull(constructorMetaDataList)|| constructorMetaDataList.isEmpty()){
      return null;
    }else{
      ConstructorMetaData constructorMetaData= null;
      for (ConstructorMetaData metaData : constructorMetaDataList) {
        if(metaData.getAccessibleObject().equals(constructor)){
          constructorMetaData=metaData;
          break;
        }
      }
      return constructorMetaData;
    }
  }

  public static ConstructorMetaData getZeroArgsConstructor(List<ConstructorMetaData> constructorMetaDataList){
    if(Objects.isNull(constructorMetaDataList)|| constructorMetaDataList.isEmpty()){
      return null;
    }else{
      ConstructorMetaData constructorMetaData = null;
      for (ConstructorMetaData metaData : constructorMetaDataList) {
        if (metaData.getParameterCount() == 0) {
          constructorMetaData = metaData;
          break;
        }
      }
      return constructorMetaData;
    }
  }

  public static ConstructorMetaData getConstructorMetaData(List<ConstructorMetaData> constructorMetaDataList) {
    ConstructorMetaData zeroArgsConstructor = getZeroArgsConstructor(constructorMetaDataList);
    if (Objects.nonNull(zeroArgsConstructor)) {
      return zeroArgsConstructor;
    } else {
      for (ConstructorMetaData constructorMetaData : constructorMetaDataList) {
        if (Objects.isNull(zeroArgsConstructor)) {
          zeroArgsConstructor = constructorMetaData;
        } else if (zeroArgsConstructor.getParameterCount() < constructorMetaData.getParameterCount()) {
          zeroArgsConstructor = constructorMetaData;
        }
      }
    }
    return zeroArgsConstructor;
  }

  public static ConstructorMetaData getZeroArgsConstructor(SPIDefinition definition) {
    return getZeroArgsConstructor(definition.getConstructorMetaDataList());
  }
}
